/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Operations.Wavelet;


import java.awt.Dimension;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.*;
import Operations.*;

/**
 *
 * @author littlechild
 */
public class Compressor {
    Wavelet wavelet=new Wavelet();
    double[][] wave;
    BufferedImage returnImageCompress;
    BufferedImage lastImage;
    int lastlevel=-1;
    public BufferedImage compress(BufferedImage image,double factor,int level,String filename) {
        if(image==null)
            return null;
        ObjectOutputStream out = null;
        if(lastImage==null || !lastImage.equals(image) || lastlevel!=level ) {
            lastlevel=level;
            lastImage=image;
            double[][] ori = new double[image.getWidth()][image.getHeight()];
            for (int i = 0; i != image.getWidth(); ++i) {
                for (int j = 0; j != image.getHeight(); ++j) {
                    ori[i][j] = CommonFunc.getGray(image.getRGB(i, j));
                }
            }
            Dimension LLsize = new Dimension();
            wave = wavelet.transform(ori, level, LLsize);
            returnImageCompress = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
            for (int i = 0; i != returnImageCompress.getWidth(); ++i) {
                for (int j = 0; j != returnImageCompress.getHeight(); ++j) {
                    if (i < LLsize.getWidth() && j < LLsize.getHeight()) {
                        int rgb = round(wave[i][j]);
                        rgb=rgb<0 ? 0 : rgb;
                        rgb=rgb>0xff ? 0xff:rgb;
                        rgb = rgb | (rgb << 8) | (rgb << 16);
                        returnImageCompress.setRGB(i, j, rgb);
                    } else {
                        int rgb = round(wave[i][j]/2+0x70);
                        rgb=rgb<0 ? 0 : rgb;
                        rgb=rgb>0xff ? 0xff:rgb;
                        rgb = rgb | (rgb << 8) | (rgb << 16);
                        returnImageCompress.setRGB(i, j, rgb);
                    }
                }
            }
        }
        WaveCompressFile wcf = new WaveCompressFile();
        wcf.data=new byte[wave.length][wave[0].length];
        wcf.factor = factor;
        wcf.level = level;
        wcf.width=image.getWidth();
        wcf.height=image.getHeight();
        writeByFactor(wcf.data,wave,factor,level);
        try {
            out = new ObjectOutputStream(new DeflaterOutputStream(new FileOutputStream(filename)));
            //out = new ObjectOutputStream(new FileOutputStream(filename));
            out.writeObject(wcf);
            out.close();
        } catch (IOException ex) {
            Logger.getLogger(Compressor.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                Logger.getLogger(Compressor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return returnImageCompress;
    }
    public BufferedImage depress(String filename) {           
        ObjectInputStream in;
        WaveCompressFile wcf;
        try {
            in = new ObjectInputStream(new InflaterInputStream(new FileInputStream(filename)));
            //in = new ObjectInputStream(new FileInputStream(filename));
            wcf = (WaveCompressFile) in.readObject();
            double [][]data=new double[wcf.data.length][wcf.data[0].length];
            readByFactor(data,wcf.data,wcf.factor,wcf.level);
            data=wavelet.invTransform(data, wcf.level);
            BufferedImage returnImage=new BufferedImage(wcf.width,wcf.height,BufferedImage.TYPE_3BYTE_BGR);
            for(int i=0;i!=returnImage.getWidth();++i) {
                for(int j=0;j!=returnImage.getHeight();++j) {
                    int rgb=round(data[i][j]);
                    rgb=(rgb<0) ? 0 : rgb;
                    rgb=(rgb>0xff) ? 0xff: rgb;
                    returnImage.setRGB(i, j, rgb|rgb<<8|rgb<<16);
                }
            }
            return returnImage;
        } catch (IOException ex) {
            Logger.getLogger(Compressor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(Compressor.class.getName()).log(Level.SEVERE, null, ex);
        }      
        return null;
    }

    private int round(double d) {
        if(d<0)
            return (int)(d-0.5);
        else
            return (int)(d+0.5);
    }

    private void writeByFactor(byte[][] data, double[][] wave,double factor,int level) {
        writeByFactor(data,wave,wave.length,wave[0].length,getK(factor,level),factor,level);
    }

    private void writeByFactor(byte[][] data, double[][] wave, int width, int height, double k, double factor, int level) {
        if(level==0) {
            for(int i=0;i!=width;++i) {
                for(int j=0;j!=height;++j) {
                    data[i][j]=(byte)round(wave[i][j]*factor);
                }
            }
            return;
        }
        //LL
        writeByFactor(data,wave,width/2+this.wavelet.getFilterType()/2
                ,height/2+this.wavelet.getFilterType()/2,k,factor*k,level-1);
        //HL
        double sfactor=factor*Math.pow(k, 1/2.0);
        for(int i=width/2+this.wavelet.getFilterType()/2;i!=width;++i) {
            for(int j=0;j!=height/2+this.wavelet.getFilterType()/2;++j) {
                data[i][j]=(byte)round(wave[i][j]*sfactor);
            }
        }
        //LH
        for(int i=0;i!=width/2+this.wavelet.getFilterType()/2;++i) {
            for(int j=height/2+this.wavelet.getFilterType()/2;j!=height;++j) {
                data[i][j]=(byte)round(wave[i][j]*sfactor);
            }
        }
        //HH
        for(int i=width/2+this.wavelet.getFilterType()/2;i!=width;++i) {
            for(int j=height/2+this.wavelet.getFilterType()/2;j!=height;++j) {
                data[i][j]=(byte)round(wave[i][j]*factor);
            }
        }
    }
    private void readByFactor(double[][] wave, byte[][] data,double factor,int level) {
        readByFactor(wave,data,data.length,data[0].length,getK(factor,level),factor,level);
    }
    private void readByFactor(double[][] wave, byte[][] data, int width, int height, double k,double factor, int level) {
        if(level==0) {
            for(int i=0;i!=width;++i) {
                for(int j=0;j!=height;++j) {
                    wave[i][j]=data[i][j]/factor;
                }
            }
            return;
        }
        //LL
        readByFactor(wave,data,width/2+this.wavelet.getFilterType()/2
                ,height/2+this.wavelet.getFilterType()/2,k,factor*k,level-1);
        //HL
        double sfactor=factor*Math.pow(k, 1/2.0);
        for(int i=width/2+this.wavelet.getFilterType()/2;i!=width;++i) {
            for(int j=0;j!=height/2+this.wavelet.getFilterType()/2;++j) {
                wave[i][j]=data[i][j]/sfactor;
            }
        }
        //LH
        for(int i=0;i!=width/2+this.wavelet.getFilterType()/2;++i) {
            for(int j=height/2+this.wavelet.getFilterType()/2;j!=height;++j) {
                wave[i][j]=data[i][j]/sfactor;
            }
        }
        //HH
        for(int i=width/2+this.wavelet.getFilterType()/2;i!=width;++i) {
            for(int j=height/2+this.wavelet.getFilterType()/2;j!=height;++j) {
                wave[i][j]=data[i][j]/factor;
            }
        }
    }
    private double getK(double factor,int level) {
        double finalRatio=Math.pow(factor,0.5);
        return Math.pow(finalRatio/factor,1.0/level);                
    }
}
